package edu.caece.langprocessor.syntax.tree;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;

import edu.caece.langprocessor.lexical.tokens.TokenType;
import edu.caece.langprocessor.syntax.GrammarItem;
import edu.caece.langprocessor.syntax.NonTerminal;
import edu.caece.langprocessor.syntax.Terminal;
import edu.caece.langprocessor.syntax.tree.nodes.AddTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.AssignmentTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.BeginTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.CallTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.ConditionEqualsTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.ConditionEvenTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.ConditionGreaterEqualsTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.ConditionGreaterTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.ConditionLowerEqualsTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.ConditionLowerTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.ConditionOddTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.ConditionTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.ConstTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.DefblockTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.DefcmdConstTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.DefcmdVarTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.DivTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.DoTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.EndOfProgramTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.EndOfStatementTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.EndTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.EqualsTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.EvenTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.ExpressionAddTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.ExpressionSubsTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.ExpressionTermTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.FactorIdTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.FactorTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.GreaterEqualsTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.GreaterTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.IdListTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.IdentifierTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.IfTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.LeftParenthesisTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.LowerEqualsTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.LowerTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.MainBlockTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.MultTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.NumericalTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.OddTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.ProcBlockTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.ProcedureTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.ProgTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.ReadlnTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.RightParenthesisTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.SeparatorTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.SimpleTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.StmtAssignmentTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.StmtCallTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.StmtIfTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.StmtListTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.StmtReadlnTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.StmtStmtsBlockTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.StmtWhileTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.StmtWriteTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.StmtWritelnTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.StmtsBlockTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.StringTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.SubsTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.TermDivTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.TermFactorTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.TermMultTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.ThenTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.VarTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.WhileTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.WrParamsTailIdTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.WrParamsTailTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.WriteTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.WritelnTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.WrparamsIdTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.WrparamsStringTreeNode;

public class TreeNodeFactory {

	private Map<TokenType, Constructor<?>> terminalNodesConstructorsMap;
	private Map<String, Constructor<?>> nonTerminalNodesConstructorsMap;

	public TreeNodeFactory() {

		terminalNodesConstructorsMap = new HashMap<TokenType, Constructor<?>>();
		terminalNodesConstructorsMap.put(TokenType.var, getConstructor(VarTreeNode.class));
		terminalNodesConstructorsMap.put(TokenType.identifier, getConstructor(IdentifierTreeNode.class));
		terminalNodesConstructorsMap.put(TokenType.assignment, getConstructor(AssignmentTreeNode.class));
		terminalNodesConstructorsMap.put(TokenType.kconst, getConstructor(ConstTreeNode.class));
		terminalNodesConstructorsMap.put(TokenType.numerical, getConstructor(NumericalTreeNode.class));
		terminalNodesConstructorsMap.put(TokenType.equals, getConstructor(EqualsTreeNode.class));
		terminalNodesConstructorsMap.put(TokenType.greater, getConstructor(GreaterTreeNode.class));
		terminalNodesConstructorsMap.put(TokenType.greaterEquals, getConstructor(GreaterEqualsTreeNode.class));
		terminalNodesConstructorsMap.put(TokenType.lower, getConstructor(LowerTreeNode.class));
		terminalNodesConstructorsMap.put(TokenType.lowerEquals, getConstructor(LowerEqualsTreeNode.class));
		terminalNodesConstructorsMap.put(TokenType.add, getConstructor(AddTreeNode.class));
		terminalNodesConstructorsMap.put(TokenType.subs, getConstructor(SubsTreeNode.class));
		terminalNodesConstructorsMap.put(TokenType.mult, getConstructor(MultTreeNode.class));
		terminalNodesConstructorsMap.put(TokenType.div, getConstructor(DivTreeNode.class));
		terminalNodesConstructorsMap.put(TokenType.begin, getConstructor(BeginTreeNode.class));
		terminalNodesConstructorsMap.put(TokenType.end, getConstructor(EndTreeNode.class));
		terminalNodesConstructorsMap.put(TokenType.kwhile, getConstructor(WhileTreeNode.class));
		terminalNodesConstructorsMap.put(TokenType.kdo, getConstructor(DoTreeNode.class));
		terminalNodesConstructorsMap.put(TokenType.kif, getConstructor(IfTreeNode.class));
		terminalNodesConstructorsMap.put(TokenType.then, getConstructor(ThenTreeNode.class));
		terminalNodesConstructorsMap.put(TokenType.call, getConstructor(CallTreeNode.class));
		terminalNodesConstructorsMap.put(TokenType.procedure, getConstructor(ProcedureTreeNode.class));
		terminalNodesConstructorsMap.put(TokenType.string, getConstructor(StringTreeNode.class));
		terminalNodesConstructorsMap.put(TokenType.readln, getConstructor(ReadlnTreeNode.class));
		terminalNodesConstructorsMap.put(TokenType.write, getConstructor(WriteTreeNode.class));
		terminalNodesConstructorsMap.put(TokenType.writeln, getConstructor(WritelnTreeNode.class));
		terminalNodesConstructorsMap.put(TokenType.odd, getConstructor(OddTreeNode.class));
		terminalNodesConstructorsMap.put(TokenType.even, getConstructor(EvenTreeNode.class));
		terminalNodesConstructorsMap.put(TokenType.endOfStatement, getConstructor(EndOfStatementTreeNode.class));
		terminalNodesConstructorsMap.put(TokenType.separator, getConstructor(SeparatorTreeNode.class));
		terminalNodesConstructorsMap.put(TokenType.endOfProgram, getConstructor(EndOfProgramTreeNode.class));
		terminalNodesConstructorsMap.put(TokenType.leftParenthesis, getConstructor(LeftParenthesisTreeNode.class));
		terminalNodesConstructorsMap.put(TokenType.rightParenthesis, getConstructor(RightParenthesisTreeNode.class));
		
		nonTerminalNodesConstructorsMap = new HashMap<String, Constructor<?>>();
		nonTerminalNodesConstructorsMap.put("prog", getConstructor(ProgTreeNode.class));
		nonTerminalNodesConstructorsMap.put("stmts_block", getConstructor(StmtsBlockTreeNode.class));
		nonTerminalNodesConstructorsMap.put("stmt_list", getConstructor(StmtListTreeNode.class));
		nonTerminalNodesConstructorsMap.put("procblock", getConstructor(ProcBlockTreeNode.class));
		nonTerminalNodesConstructorsMap.put("mainblock", getConstructor(MainBlockTreeNode.class));
		nonTerminalNodesConstructorsMap.put("defblock", getConstructor(DefblockTreeNode.class));
		nonTerminalNodesConstructorsMap.put("factor", getConstructor(FactorTreeNode.class));
		nonTerminalNodesConstructorsMap.put("wrparams_tail", getConstructor(WrParamsTailTreeNode.class));
		nonTerminalNodesConstructorsMap.put("id_list", getConstructor(IdListTreeNode.class));
		nonTerminalNodesConstructorsMap.put("factorid", getConstructor(FactorIdTreeNode.class));
		nonTerminalNodesConstructorsMap.put("defcmdvar", getConstructor(DefcmdVarTreeNode.class));
		nonTerminalNodesConstructorsMap.put("defcmdconst", getConstructor(DefcmdConstTreeNode.class));
		nonTerminalNodesConstructorsMap.put("stmtstmtsblock", getConstructor(StmtStmtsBlockTreeNode.class));
		nonTerminalNodesConstructorsMap.put("stmtif", getConstructor(StmtIfTreeNode.class));
		nonTerminalNodesConstructorsMap.put("stmtwhile", getConstructor(StmtWhileTreeNode.class));
		nonTerminalNodesConstructorsMap.put("stmtwriteln", getConstructor(StmtWritelnTreeNode.class));
		nonTerminalNodesConstructorsMap.put("stmtwrite", getConstructor(StmtWriteTreeNode.class));
		nonTerminalNodesConstructorsMap.put("stmtreadln", getConstructor(StmtReadlnTreeNode.class));
		nonTerminalNodesConstructorsMap.put("stmtcall", getConstructor(StmtCallTreeNode.class));
		nonTerminalNodesConstructorsMap.put("stmtassignment", getConstructor(StmtAssignmentTreeNode.class));
		nonTerminalNodesConstructorsMap.put("expradd", getConstructor(ExpressionAddTreeNode.class));
		nonTerminalNodesConstructorsMap.put("exprsubs", getConstructor(ExpressionSubsTreeNode.class));		
		nonTerminalNodesConstructorsMap.put("exprterm", getConstructor(ExpressionTermTreeNode.class));		
		nonTerminalNodesConstructorsMap.put("termmult", getConstructor(TermMultTreeNode.class));
		nonTerminalNodesConstructorsMap.put("termdiv", getConstructor(TermDivTreeNode.class));
		nonTerminalNodesConstructorsMap.put("termfactor", getConstructor(TermFactorTreeNode.class));
		nonTerminalNodesConstructorsMap.put("condition", getConstructor(ConditionTreeNode.class));
		nonTerminalNodesConstructorsMap.put("conditionequals", getConstructor(ConditionEqualsTreeNode.class));
		nonTerminalNodesConstructorsMap.put("conditiongreater", getConstructor(ConditionGreaterTreeNode.class));
		nonTerminalNodesConstructorsMap.put("conditiongreaterequals", getConstructor(ConditionGreaterEqualsTreeNode.class));
		nonTerminalNodesConstructorsMap.put("conditionlower", getConstructor(ConditionLowerTreeNode.class));
		nonTerminalNodesConstructorsMap.put("conditionlowerequals", getConstructor(ConditionLowerEqualsTreeNode.class));
		nonTerminalNodesConstructorsMap.put("conditioneven", getConstructor(ConditionEvenTreeNode.class));
		nonTerminalNodesConstructorsMap.put("conditionodd", getConstructor(ConditionOddTreeNode.class));
		nonTerminalNodesConstructorsMap.put("wrparamsstring", getConstructor(WrparamsStringTreeNode.class));
		nonTerminalNodesConstructorsMap.put("wrparamstailid", getConstructor(WrParamsTailIdTreeNode.class));
		nonTerminalNodesConstructorsMap.put("wrparamsid", getConstructor(WrparamsIdTreeNode.class));
		/*	id_list_tail, stmt_list_tail, */
	}

	private Constructor<?> getConstructor(Class<?> treeNodeClass) {
		Constructor<?> c;
		try {
			c = treeNodeClass.getConstructor(GrammarItem.class);
		} catch (SecurityException e) {
			throw new RuntimeException(e.getMessage());
		} catch (NoSuchMethodException e) {
			throw new RuntimeException(e.getMessage());
		}
		return c;
	}

	public TreeNode getNewInstance(GrammarItem grammarItem) {
		TreeNode stn = null;
		if (grammarItem instanceof NonTerminal){
			NonTerminal nt = (NonTerminal) grammarItem;
			stn = this.createNonTerminalTreeNode(nt.getValue(), nt);
		}else {
			Terminal t = (Terminal) grammarItem;
			TokenType token = t.getLexicalToken().getToken();
			stn = this.createTerminalTreeNode(token, t);
		}
		return stn;
	}

	public TreeNode getNewInstance(String productionClass, NonTerminal nonTerminal) {
		TreeNode stn = null;
		stn = this.createNonTerminalTreeNode(productionClass, nonTerminal);
		return stn;
	}
	
	private TreeNode createNonTerminalTreeNode(String value, NonTerminal nonTerminal) {
		TreeNode stn = null;
		if (nonTerminalNodesConstructorsMap.containsKey(value)) {
			Constructor<?> c = nonTerminalNodesConstructorsMap.get(value);
			try {
				stn = (TreeNode) c.newInstance(nonTerminal);
			} catch (IllegalArgumentException e) {
				throw new RuntimeException(e.getMessage());
			} catch (InstantiationException e) {
				throw new RuntimeException(e.getMessage());
			} catch (IllegalAccessException e) {
				throw new RuntimeException(e.getMessage());
			} catch (InvocationTargetException e) {
				throw new RuntimeException(e.getMessage());
			}
		} else {
			stn = new SimpleTreeNode(nonTerminal);
		}
		return stn;
	}

	private TreeNode createTerminalTreeNode(TokenType token, Terminal terminal) {
		TreeNode stn = null;

		if (terminalNodesConstructorsMap.containsKey(token)) {
			Constructor<?> c = terminalNodesConstructorsMap.get(token);
			try {
				stn = (TreeNode) c.newInstance(terminal);
			} catch (IllegalArgumentException e) {
				throw new RuntimeException(e.getMessage());
			} catch (InstantiationException e) {
				throw new RuntimeException(e.getMessage());
			} catch (IllegalAccessException e) {
				throw new RuntimeException(e.getMessage());
			} catch (InvocationTargetException e) {
				throw new RuntimeException(e.getMessage());
			}
		} else {
			stn = new SimpleTreeNode(terminal);
		}
		return stn;
	}
}
